package br.great.multimidia;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

import org.apache.sanselan.Sanselan;
import org.apache.sanselan.common.byteSources.ByteSource;
import org.apache.sanselan.common.byteSources.ByteSourceFile;
import org.apache.sanselan.formats.jpeg.JpegImageMetadata;
import org.apache.sanselan.formats.jpeg.JpegImageParser;
import org.apache.sanselan.formats.jpeg.JpegPhotoshopMetadata;
import org.apache.sanselan.formats.jpeg.exifRewrite.ExifRewriter;
import org.apache.sanselan.formats.jpeg.iptc.IPTCConstants;
import org.apache.sanselan.formats.jpeg.iptc.IPTCRecord;
import org.apache.sanselan.formats.jpeg.iptc.JpegIptcRewriter;
import org.apache.sanselan.formats.jpeg.iptc.PhotoshopApp13Data;
import org.apache.sanselan.formats.tiff.TiffImageMetadata;
import org.apache.sanselan.formats.tiff.write.TiffOutputSet;

@SuppressWarnings({ "unchecked", "rawtypes" })
public class PhotoMetaData {

    private File file;
    private ByteSource byteSource;
    private JpegPhotoshopMetadata photoshopMetadata;
    private TiffOutputSet tiffOutputSet;

    public PhotoMetaData(String path) {
        this(new File(path));
    }

    public PhotoMetaData(File photoFile) {
        this.file = photoFile;
        byteSource = new ByteSourceFile(file);
        initPhotoshopMetadata();
        initTiffOutputSet();
    }

    private void initTiffOutputSet() {
        try {
            JpegImageMetadata jpegMetadata = (JpegImageMetadata) Sanselan.getMetadata(file);

            if (jpegMetadata != null) {
                TiffImageMetadata exif = jpegMetadata.getExif();

                if (exif != null) {
                    tiffOutputSet = exif.getOutputSet();
                }
            }

            if (tiffOutputSet == null) {
                tiffOutputSet = new TiffOutputSet();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void initPhotoshopMetadata() {
        try {
            photoshopMetadata = new JpegImageParser().getPhotoshopMetadata(byteSource, null);
            if (photoshopMetadata == null) {
                photoshopMetadata = new JpegPhotoshopMetadata(new PhotoshopApp13Data(new ArrayList(), new ArrayList()));
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void addIPTCKeyword(String... newKeywords) {

        List<IPTCRecord> newIPTCRecords = updateIPTCKeywordsCollection(newKeywords);
        persist(newIPTCRecords);
    }

    public void setDescription(String description) {
        List<IPTCRecord> iptcRecords = photoshopMetadata.photoshopApp13Data.getRecords();

        iptcRecords.add(new IPTCRecord(IPTCConstants.IPTC_TYPE_CAPTION_ABSTRACT, description));
        persist(iptcRecords);
    }

    public void setGpsCoordinates(String latitude, String longitude) {

        ByteArrayOutputStream newDataBAOS = new ByteArrayOutputStream();
        try {
            tiffOutputSet.setGPSInDegrees(Double.parseDouble(longitude), Double.parseDouble(latitude));
            new ExifRewriter().updateExifMetadataLossy(file, newDataBAOS, tiffOutputSet);
            updateFileData(newDataBAOS.toByteArray());

        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            closeOutputStream(newDataBAOS);
        }
    }

    private void closeOutputStream(OutputStream outputStream) {
        if (outputStream != null) {
            try {
                outputStream.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private void persist(List<IPTCRecord> iptcRecords) {
        byte[] byteArray = updateIPTCData(iptcRecords);
        updateFileData(byteArray);
    }

    private List<IPTCRecord> updateIPTCKeywordsCollection(String... newKeywords) {
        PhotoshopApp13Data photoshopApp13Data = photoshopMetadata.photoshopApp13Data;
        List<IPTCRecord> iptcRecords = photoshopApp13Data.getRecords();

        for (String keyword : newKeywords) {
            iptcRecords.add(new IPTCRecord(IPTCConstants.IPTC_TYPE_KEYWORDS, keyword));
        }
        return iptcRecords;
    }

    private byte[] updateIPTCData(List<IPTCRecord> iptcRecords) {
        PhotoshopApp13Data newImageData = new PhotoshopApp13Data(iptcRecords, photoshopMetadata.photoshopApp13Data.getNonIptcBlocks());

        ByteArrayOutputStream newDataBAOS = new ByteArrayOutputStream();
        try {
            new JpegIptcRewriter().writeIPTC(byteSource, newDataBAOS, newImageData);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return newDataBAOS.toByteArray();
    }

    private void updateFileData(byte[] byteArray) {

        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(file);
            fileOutputStream.write(byteArray);
            fileOutputStream.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            closeOutputStream(fileOutputStream);
        }
    }
}
